/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;
using CsJni.PlatformAbstraction;

namespace CsJni
{
    internal sealed class JavaVMLoader
    {
        // _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *arguments);
        [UnmanagedFunctionPointer(JavaVM.CC)]
        private unsafe delegate int JNI_CreateJavaVM_Handler(void*** pppvm, void*** pppenv, void* arguments);

        // _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);
        [UnmanagedFunctionPointer(JavaVM.CC)]
        private unsafe delegate int JNI_GetCreatedJavaVMs_Handler(void*** pppvm, int bufferLength, int* countVMs);

        private string mJavaVMPath;
        private IntPtr mJavaVMModuleHandle;
        private JavaVMConfiguration mJavaVMConfiguration;

        public JavaVMLoader(JavaVMConfiguration configuration)
        {
            mJavaVMConfiguration = configuration;
            mJavaVMPath = JavaVMPathFinder.FindPath(configuration);
            mJavaVMModuleHandle = PlatformFactory.Platform.LoadDynamicLibrary(mJavaVMPath);
            if (JavaVM.IsLoggingEnabled)
            {
                JavaVM.Log("Loaded JVM DLL: " + mJavaVMPath);
            }
        }

        public IntPtr GetFirstCreatedVM()
        {
            return GetFirstCreatedVM(mJavaVMModuleHandle);
        }

        public IntPtr Load(out IntPtr environmentHandle)
        {
            environmentHandle = IntPtr.Zero;

            unsafe
            {
                void** ppEnv = null;
                void** ppJavaVM = null;

                JavaVMInitArgs arguments = new JavaVMInitArgs();
                arguments.ignoreUnrecognized = JBooleanConverter.To(mJavaVMConfiguration.IgnoreUnrecognized);
                arguments.version = (int)mJavaVMConfiguration.Version;
                arguments.nOptions = mJavaVMConfiguration.OptionArray.Length;
                try
                {
                    arguments.options = mJavaVMConfiguration.OptionArray.Pin();
                    int result = CreateVM(mJavaVMModuleHandle, &ppJavaVM, &ppEnv, &arguments);
                    if (0 != result)
                    {
                        JniException.CheckVMErrorCode(result);
                    }
                }
                finally
                {
                    mJavaVMConfiguration.OptionArray.Free();
                }

                IntPtr javaVM = new IntPtr((void*)ppJavaVM);
                environmentHandle = new IntPtr((void*)ppEnv);
                return javaVM;
            }
        }

        private static IntPtr GetFirstCreatedVM(IntPtr javaVMModuleHandle)
        {
            const int bufferSize = 4;
            unsafe
            {
                IntPtr* pJavaVMBuffer = stackalloc IntPtr[bufferSize];
                int countVMs = 0;
                int result = GetCreatedVMs(javaVMModuleHandle,
                    (void***)pJavaVMBuffer,
                    bufferSize, &countVMs);
                if (0 != result)
                {
                    JniException.CheckVMErrorCode(result);
                }
                if (countVMs > 0)
                {
                    void** ppJavaVM = (void**)(*(pJavaVMBuffer + 0)).ToPointer();
                    IntPtr javaVM = new IntPtr((void*)ppJavaVM);
                    return javaVM;
                }
                return IntPtr.Zero;
            }
        }

        // See https://bugzilla.novell.com/show_bug.cgi?id=597189 - I entered this bug about not 
        // being able to debug in MonoDevelop, fixed quickly on their SVN branch.
        private static unsafe int GetCreatedVMs(IntPtr javaVMModuleHandle, void*** pppvm, int bufferLength, int* countVMs)
        {
            JNI_GetCreatedJavaVMs_Handler getCreatedHandler = PlatformFactory.Platform.GetDelegateForLibraryMethod<JNI_GetCreatedJavaVMs_Handler>(
                javaVMModuleHandle, PlatformFactory.Platform.GetCreatedJavaVMsMethodName);
            return getCreatedHandler(pppvm, bufferLength, countVMs);
        }

        private static unsafe int CreateVM(IntPtr javaVMModuleHandle, void*** pppvm, void*** pppenv, void* arguments)
        {
            JNI_CreateJavaVM_Handler createVMHandler = PlatformFactory.Platform.GetDelegateForLibraryMethod<JNI_CreateJavaVM_Handler>(
                javaVMModuleHandle, PlatformFactory.Platform.CreateJavaVMMethodName);
            return createVMHandler(pppvm, pppenv, arguments);
        }
    }
}
